package cn.piesat.scanning.utils;

import net.sf.sevenzipjbinding.*;
import net.sf.sevenzipjbinding.impl.RandomAccessFileInStream;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;

import java.io.*;
import java.util.Enumeration;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * 解压文件工具类
 */
public class UnZipUtil {

    public static boolean checkFileNameType(File file,String targetPath){
        boolean status = false;
        if (file.toString().endsWith("tar.bz2")) {
            status = decompressTarBz2(file, targetPath, false);
        } else if (file.toString().endsWith("bz2")) {
            status = decompressBZ2(file, targetPath, false);
        } else if (file.toString().endsWith("tar.gz")) {
            status = decompressTarGz(file, targetPath, false);
        } else if (file.toString().endsWith("gz")) {
            status = decompressGz(file, targetPath, false);
        } else if (file.toString().endsWith("zip")) {
            status = upZipFile(file, targetPath);
        } else if (file.toString().endsWith("7z")) {
            status = decompress7Z(file, targetPath, false);
        } else if (file.toString().endsWith("tar")) {
            status = decompressTar(file, targetPath, false);
        } else if (file.toString().endsWith("rar")) {
            status = decompressRar5(file, targetPath, false);
        }
        return status;
    }

    /**
     * 解压缩bz2文件
     * @param file 压缩包文件
     * @param targetPath 目标文件夹
     * @param delete 解压后是否删除原压缩包文件
     */
    public static boolean decompressBZ2(File file, String targetPath, boolean delete){
        FileInputStream fis = null;
        OutputStream fos = null;
        BZip2CompressorInputStream bis = null;
        String suffix = ".bz2";
        try {
            fis = new FileInputStream(file);
            bis = new BZip2CompressorInputStream(fis);
            // 创建输出目录
            createDirectory(targetPath, null);
            File tempFile = new File(targetPath + File.separator + file.getName().replace(suffix, ""));
            fos = new FileOutputStream(tempFile);

            int count;
            byte data[] = new byte[2048];
            while ((count = bis.read(data)) != -1) {
                fos.write(data, 0, count);
            }
            fos.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                if(fis != null){
                    fis.close();
                }
                if(fos != null){
                    fos.close();
                }
                if(bis != null){
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩gz文件
     * @param file 压缩包文件
     * @param targetPath 目标文件夹
     * @param delete 解压后是否删除原压缩包文件
     */
    private static boolean decompressGz(File file, String targetPath,  boolean delete){
        FileInputStream  fileInputStream = null;
        GZIPInputStream gzipIn = null;
        OutputStream out = null;
        String suffix = ".gz";
        try {
            fileInputStream = new FileInputStream(file);
            gzipIn = new GZIPInputStream(fileInputStream);
            // 创建输出目录
            createDirectory(targetPath, null);

            File tempFile = new File(targetPath + File.separator + file.getName().replace(suffix, ""));
            out = new FileOutputStream(tempFile);
            int count;
            byte data[] = new byte[2048];
            while ((count = gzipIn.read(data)) != -1) {
                out.write(data, 0, count);
            }
            out.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                if(out != null){
                    out.close();
                }
                if(gzipIn != null){
                    gzipIn.close();
                }
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩7z文件
     * @param file 压缩包文件
     * @param targetPath 目标文件夹
     * @param delete 解压后是否删除原压缩包文件
     */
    private static boolean decompress7Z(File file, String targetPath,  boolean delete){
        SevenZFile sevenZFile = null;
        OutputStream outputStream = null;
        try {
            sevenZFile = new SevenZFile(file);
            // 创建输出目录
            createDirectory(targetPath, null);
            SevenZArchiveEntry entry;

            while((entry = sevenZFile.getNextEntry()) != null){
                if(entry.isDirectory()){
                    createDirectory(targetPath, entry.getName()); // 创建子目录
                }else{
                    outputStream = new FileOutputStream(new File(targetPath + File.separator + entry.getName()));
                    int len = 0;
                    byte[] b = new byte[2048];
                    while((len = sevenZFile.read(b)) != -1){
                        outputStream.write(b, 0, len);
                    }
                    outputStream.flush();
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }finally {
            try {
                if(sevenZFile != null){
                    sevenZFile.close();
                }
                if(outputStream != null){
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩一个文件
     * @param zipFile 压缩文件
     * @param folderPath 解压缩的目标目录
     * @throws IOException
     * @throws ZipException
     */
    public static boolean upZipFile(File zipFile, String folderPath) {
        // 根据路径创建一个文件
        File desDir = new File(folderPath);
        // 判断文件是否存在，如果不存在则创建
        if (!desDir.exists()) {
            desDir.mkdirs();
        }
        InputStream in = null;
        OutputStream out = null;
        try {
            // 创建一个压缩文件
            ZipFile zFile = new ZipFile(zipFile);
            // 循环遍历
            for (Enumeration<?> entries = zFile.entries(); entries.hasMoreElements(); ){
                ZipEntry entry = (ZipEntry) entries.nextElement();
                in = zFile.getInputStream(entry);
                String str = folderPath + File.separator + entry.getName();
                str = new String(str.getBytes("8859_1"), "GB2312");
                File desFile = new File(str);
                // 判断文件是否存在
                if (!desFile.exists()) {
                    File fileParentDir = desFile.getParentFile();
                    // 判断父文件夹是否存在
                    if (!fileParentDir.exists()) {
                        fileParentDir.mkdirs();
                    }
                    // 创建新文件
                    desFile.createNewFile();
                }
                out = new FileOutputStream(desFile);
                byte[] buffer = new byte[1024];
                int realLength;
                while ((realLength = in.read(buffer)) >0) {
                    out.write(buffer, 0, realLength);
                }
                in.close();
                out.close();
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }finally {
            if (in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }

    /**
     * 解压缩tar文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    private static boolean decompressTar(File file, String targetPath, boolean delete) {
        FileInputStream fis = null;
        OutputStream fos = null;
        TarInputStream tarInputStream = null;
        try {
            fis = new FileInputStream(file);
            tarInputStream = new TarInputStream(fis, 1024 * 2);
            // 创建输出目录
            createDirectory(targetPath, null);

            TarEntry entry;
            while (true) {
                entry = tarInputStream.getNextEntry();
                if (entry == null) {
                    break;
                }
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName()); // 创建子目录
                } else {
                    fos = new FileOutputStream(new File(targetPath + File.separator + entry.getName()));
                    int count;
                    byte data[] = new byte[2048];
                    while ((count = tarInputStream.read(data)) != -1) {
                        fos.write(data, 0, count);
                    }
                    fos.flush();
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (tarInputStream != null) {
                    tarInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩tar.gz文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    private static boolean decompressTarGz(File file, String targetPath, boolean delete) {
        FileInputStream fileInputStream = null;
        BufferedInputStream bufferedInputStream = null;
        GZIPInputStream gzipIn = null;
        TarInputStream tarIn = null;
        OutputStream out = null;
        try {
            fileInputStream = new FileInputStream(file);
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            gzipIn = new GZIPInputStream(bufferedInputStream);
            tarIn = new TarInputStream(gzipIn, 1024 * 2);

            // 创建输出目录
            createDirectory(targetPath, null);

            TarEntry entry = null;
            while ((entry = tarIn.getNextEntry()) != null) {
                if (entry.isDirectory()) { // 是目录
                    createDirectory(targetPath, entry.getName()); // 创建子目录
                } else { // 是文件
                    File tempFIle = new File(targetPath + File.separator + entry.getName());
                    createDirectory(tempFIle.getParent() + File.separator, null);
                    out = new FileOutputStream(tempFIle);
                    int len = 0;
                    byte[] b = new byte[2048];

                    while ((len = tarIn.read(b)) != -1) {
                        out.write(b, 0, len);
                    }
                    out.flush();
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (tarIn != null) {
                    tarIn.close();
                }
                if (gzipIn != null) {
                    gzipIn.close();
                }
                if (bufferedInputStream != null) {
                    bufferedInputStream.close();
                }
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩tar.bz2文件
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    public static boolean decompressTarBz2(File file, String targetPath, boolean delete) {
        FileInputStream fis = null;
        OutputStream fos = null;
        BZip2CompressorInputStream bis = null;
        TarInputStream tis = null;
        try {
            fis = new FileInputStream(file);
            bis = new BZip2CompressorInputStream(fis);
            tis = new TarInputStream(bis, 1024 * 2);
            // 创建输出目录
            createDirectory(targetPath, null);
            TarEntry entry;
            while ((entry = tis.getNextEntry()) != null) {
                if (entry.isDirectory()) {
                    createDirectory(targetPath, entry.getName()); // 创建子目录
                } else {
                    fos = new FileOutputStream(new File(targetPath + File.separator + entry.getName()));
                    int count;
                    byte data[] = new byte[2048];
                    while ((count = tis.read(data)) != -1) {
                        fos.write(data, 0, count);
                    }
                    fos.flush();
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (tis != null) {
                    tis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解压缩RAR文件，兼容RAR5版本
     *
     * @param file       压缩包文件
     * @param targetPath 目标文件夹
     * @param delete     解压后是否删除原压缩包文件
     */
    private static boolean decompressRar5(File file, String targetPath, boolean delete) {
        RandomAccessFile randomAccessFile = null;
        IInArchive inArchive;
        RandomAccessFileInStream randomAccessFileInStream = null;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
            randomAccessFileInStream = new RandomAccessFileInStream(randomAccessFile);
            inArchive = SevenZip.openInArchive(null, randomAccessFileInStream);
            int[] in = new int[inArchive.getNumberOfItems()];
            for (int i = 0; i < in.length; i++) {
                in[i] = i;
            }
            inArchive.extract(in, false, new ExtractCallback(inArchive, targetPath));
            return true;
        } catch (FileNotFoundException | SevenZipException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
                if (randomAccessFileInStream != null) {
                    randomAccessFileInStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }


        }
    }

    /**
     *  构建目录
     * @param outputDir 输出目录
     * @param subDir 子目录
     */
    private static void createDirectory(String outputDir, String subDir){
        File file = new File(outputDir);
        if(!(subDir == null || subDir.trim().equals(""))) {//子目录不为空
            file = new File(outputDir + File.separator + subDir);
        }
        if(!file.exists()){
            if(!file.getParentFile().exists()){
                file.getParentFile().mkdirs();
            }
            file.mkdirs();
        }
    }

    static class ExtractCallback implements IArchiveExtractCallback {

        private final IInArchive inArchive;
        private final String ourDir;

        public ExtractCallback(IInArchive inArchive, String ourDir) {
            this.inArchive = inArchive;
            this.ourDir = ourDir;
        }

        @Override
        public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException {
            final String path = (String) inArchive.getProperty(index, PropID.PATH);
            final boolean isFolder = (boolean) inArchive.getProperty(index, PropID.IS_FOLDER);
            return data -> {
                try {
                    if (!isFolder) {
                        File file = new File(ourDir + File.separator + path);
                        save2File(file, data);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return data.length;
            };
        }

        @Override
        public void prepareOperation(ExtractAskMode extractAskMode) {
        }

        @Override
        public void setOperationResult(ExtractOperationResult extractOperationResult) {
        }

        @Override
        public void setTotal(long total) {
        }

        @Override
        public void setCompleted(long complete) {
        }

        public static boolean save2File(File file, byte[] msg) {
            OutputStream fos = null;
            try {
                File parent = file.getParentFile();
                if ((!parent.exists()) && (!parent.mkdirs())) {
                    return false;
                }
                fos = new FileOutputStream(file);
                fos.write(msg);
                fos.flush();
                return true;
            } catch (FileNotFoundException e) {
                return false;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
